/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package p2a;

/**
 *
 * @author user
 */
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.EventListener;
import java.util.EventObject;
import java.util.Vector;
import javax.swing.event.EventListenerList;
import jpcap.JpcapCaptor;
import jpcap.JpcapSender;
import jpcap.NetworkInterface;
import jpcap.NetworkInterfaceAddress;
import jpcap.packet.*;
/**
 * @author Michael Netter
 */
 
public class TestEngine {
  public static int ARPTIMEOUT = 4000;
  public static int ICMPTIMEOUT = 4000;  
 
  public static void main(String[] argv){
    //System.out.println(new TestEngine().test(argv[0]));
  }
 
  /**
   * Send an ICMP packet (type 8) (ping) to the provided address
   * @param dst_ip Destination IP / hostname
   * @return true/false
   */
  public boolean test(TestCase test_case, int txDeviceIndex, int rxDeviceIndex) throws IOException{
    return new PacketSender().test(test_case, txDeviceIndex, rxDeviceIndex);
  }
 
  /**
   * Send an ICMP packet (type 8) (ping) to the provided address
   * @param dst_ip Destination IP / hostname
   * @param timeout Timeout for receiving a response in milliseconds
   * @return
   */
  public boolean test(TestCase test_case,int txDeviceIndex, int rxDeviceIndex, int timeout) throws IOException{
    ARPTIMEOUT = timeout;
    ICMPTIMEOUT = timeout;
    return new PacketSender().test(test_case,txDeviceIndex,rxDeviceIndex);
  }
 
  /**
   * Class for assembling and sending ARP and ICMP packets
   */
  private class PacketSender implements  TestCasePacketListener{
    private boolean testcaseReplyArrived = false;
    private InetAddress src_ip = null;
    private ARPPacket arpReplyPacket = null;
    private String dest_ip = null;
 
 
    public boolean test(TestCase test_case,int txDeviceIndex, int rxDeviceIndex) throws IOException{
     // this.dest_ip = dest_ip;
      NetworkInterface[] devices = JpcapCaptor.getDeviceList();


      JpcapCaptor captor = JpcapCaptor.openDevice(devices[rxDeviceIndex],2000,false,3000);
      JpcapSender sender = JpcapSender.openDevice(devices[txDeviceIndex]);
 
      // create packet receiver and register observer
      PacketReceiver receiver = new PacketReceiver(captor);
      receiver.addTestCaseListener(this);
      Thread packetReceiverThread = new Thread(receiver);
      packetReceiverThread.start();

        // MAC detection successful continue with test Packet construction
        Packet testPacket = assembleTestCasePacket(devices[txDeviceIndex],test_case);
        // send packet
        sender.sendPacket(testPacket);
        System.out.println("packet sent");
 
        // wait some milliseconds for a icmp response
        try {
          Thread.sleep(ICMPTIMEOUT);
        } catch (InterruptedException e) {
          System.out.println("excption timout");
        }
 
        packetReceiverThread.stop();  
        
        System.out.println("reply arrived = "+testcaseReplyArrived);
        return (testcaseReplyArrived ? true : false);
 
     /* }
      else{
        packetReceiverThread.stop();
        return false;
      }*/
    }
 
    /**
     * Function required by the  TestCasePacketListener interface
     */
        @Override
    public void testcasePacketArrived(testcasePacketArrivedEvent event){      
      ICMPPacket icmpPacket = event.getTestCasePacket();
      InetAddress dst_ip =null;
      try {
        dst_ip = InetAddress.getByName(dest_ip);
      } catch (UnknownHostException e) {
        e.printStackTrace();
      }
      if(icmpPacket.src_ip.equals(dst_ip) && icmpPacket.type == ICMPPacket.ICMP_ECHOREPLY){
        testcaseReplyArrived = true;
      }
    }
 
    /**
     * Construct a valid PING packet (ICMP type 8)
     * @param device
     * @param dst_mac
     * @return
     */
    public Packet assembleTestCasePacket(NetworkInterface device, TestCase test_case) throws IOException{

        Packet p = new Packet();
        int srcPort = Helper.preparePort(test_case.getSrcPort());
            int destPort = Helper.preparePort(test_case.getDestPort());
            String srcIP =  Helper.prepareIP(test_case.getSrcIp());
            String destIP = Helper.prepareIP(test_case.getDestIp());
            boolean rst = false, syn = false, fin = false, ack = false;
            String data = "test case "+test_case.getTestCaseNumber();
            String protocol = test_case.getProtocol().toUpperCase();
           /* if(protocol.equals("TCP"))
            {
                  if(test_case.getFlag().equalsIgnoreCase("rst"))
                    rst = true;
                else if(test_case.getFlag().equalsIgnoreCase("syn"))
                    syn = true;
                 else if(test_case.getFlag().equalsIgnoreCase("fin"))
                    fin = true;
                 else if(test_case.getFlag().equalsIgnoreCase("ack"))
                    ack = true;
                 else 
                     fin = true;
            }
            */
            if(test_case.getProtocol().equalsIgnoreCase("TCP"))
               p = prepareTCPPacket(device, srcIP, srcPort, destIP, destPort, rst, syn, fin, ack, data);
            else if(test_case.getProtocol().equalsIgnoreCase("UDP"))
               p = prepareUDPPacket(device, srcIP, srcPort, destIP, destPort, data);
            
            return p;
    }
 
     public Packet prepareUDPPacket(NetworkInterface device, String srcIP, int srcPort, String destIP, int destPort, String data) throws IOException {
        NetworkInterface[] devices = JpcapCaptor.getDeviceList();
        JpcapSender sender = JpcapSender.openDevice(device);

        UDPPacket p = new UDPPacket(srcPort, destPort);
        p.setIPv4Parameter(0, false, false, false, 0, false, false, false, 0, 1010101, 100, IPPacket.IPPROTO_UDP,
                InetAddress.getByName(srcIP), InetAddress.getByName(destIP));
        p.data = data.getBytes();

        EthernetPacket ether = new EthernetPacket();
        ether.frametype = EthernetPacket.ETHERTYPE_IP;
        ether.src_mac = new byte[]{(byte) 0, (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5};
        ether.dst_mac = new byte[]{(byte) 0, (byte) 6, (byte) 7, (byte) 8, (byte) 9, (byte) 10};
        p.datalink = ether;

        return p;
    }

    public Packet prepareTCPPacket(NetworkInterface device, String srcIP, int srcPort, String destIP, int destPort,
            boolean RST, boolean SYN, boolean FIN, boolean ACK, String data) throws IOException{

        NetworkInterface[] devices = JpcapCaptor.getDeviceList();

 

        //default
        //ack = false
        //rst = false;
        //syn = false;
        //fin = true;

        TCPPacket p = new TCPPacket(srcPort, destPort, 56, 78, false, ACK, false, RST, SYN, FIN, true, true, 10, 10);

        //int priority, boolean d_flag, boolean t_flag, boolean r_flag, int rsv_tos, boolean rsv_frag, boolean dont_frag, boolean more_frag, int offset, int ident, int ttl, int protocol, InetAddress src, InetAddress dst
        p.setIPv4Parameter(0, false, false, false, 0, false, false, false, 0, 1010101, 100, IPPacket.IPPROTO_TCP,
                InetAddress.getByName(srcIP), InetAddress.getByName(destIP));
        p.data = (data).getBytes();

        EthernetPacket ether = new EthernetPacket();
        ether.frametype = EthernetPacket.ETHERTYPE_IP;
        ether.src_mac = new byte[]{(byte) 0, (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5};
        ether.dst_mac = new byte[]{(byte) 0, (byte) 6, (byte) 7, (byte) 8, (byte) 9, (byte) 10};

        p.datalink = ether;
        

        return p;
    }
    /**
     * Select the probable correct interface
     * @return NetworkInterface
     */
    public NetworkInterface determineDevice(){
      NetworkInterface dev = null;
      for(NetworkInterface device : JpcapCaptor.getDeviceList()){      
        for(NetworkInterfaceAddress address : device.addresses){
          if(address.address instanceof Inet4Address && !address.address.toString().equalsIgnoreCase("/0.0.0.0") ){
            dev = device;    
            break;
          }
        }        
      }
      return dev;
    }
 
    /**
     * get src_ip
     * @param device
     * @return
     */
    public InetAddress determineSRCIP(NetworkInterface device){
      // determine src_ip
      InetAddress srcip=null;
      for(NetworkInterfaceAddress addr:device.addresses){
        if(addr.address instanceof Inet4Address){
          srcip=addr.address;
          break;
        }
      }
      return srcip;
    }
 
    /**
     * Open JpcapCaptor
     * @param device
     * @return JpcapCaptor
     */
    public JpcapCaptor openDevice(NetworkInterface device){
      JpcapCaptor captor = null;
      try {
        captor = JpcapCaptor.openDevice(device,2000,false,3000);
      } catch (IOException e) {
        e.printStackTrace();
      }
      return captor;
    }
 
    /**
     * Get JpcapSender
     * @param captor
     * @return JpcapSender
     */
    public JpcapSender getSender(JpcapCaptor captor){
      return captor.getJpcapSenderInstance();
    }
  }
 
 
  /**
   * Packet receiver implemented as a seperate thread
   * Register observers to get notified of received packets
   */
  private class PacketReceiver implements Runnable{
    private JpcapCaptor captor;
    private EventListenerList listeners = new EventListenerList();  
 
    public PacketReceiver(JpcapCaptor captor){
      this.captor = captor;
    }  
 
    public void addTestCaseListener( TestCasePacketListener listener ) {
      listeners.add( TestCasePacketListener.class, listener );
    }
 
    public void removeTestCaseListener( TestCasePacketListener listener ){
      listeners.remove( TestCasePacketListener.class, listener );
    }
    protected synchronized void notifyTestCaseListener( testcasePacketArrivedEvent event )  {
      for ( TestCasePacketListener l : listeners.getListeners( TestCasePacketListener.class )){
        l.testcasePacketArrived(event);
      }
    } 
 
        @Override
    public void run(){
      while(true){
        Packet packet = captor.getPacket();
        if(packet != null){
            
            System.out.println("packet recieved");
          ICMPPacket icmpPacket = (ICMPPacket)packet;
          notifyTestCaseListener(new testcasePacketArrivedEvent(this,icmpPacket));
        }
      }
    }
  }
  
  private interface TestCasePacketListener extends EventListener{
    void testcasePacketArrived(testcasePacketArrivedEvent event);
  }
 

  private class testcasePacketArrivedEvent extends EventObject{
    private ICMPPacket icmpPacket;
    public testcasePacketArrivedEvent(Object source, ICMPPacket icmpPacket){
      super(source);
      this.icmpPacket = icmpPacket;
    }
    public ICMPPacket getTestCasePacket(){
      return icmpPacket;
    }
  }
}